/**
 * @preserve jQuery Multiple Select Box Plugin %Revision%
 * 
 * http://plugins.jquery.com/project/jquerymultipleselectbox
 * http://code.google.com/p/jquerymultipleselectbox/
 * 
 * Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0
 * 
 * @author Dreamltf
 * @date %BuiltDate%
 * 
 * Depends: jquery.js (1.2+)
 */
(function($) {
	var PLUGIN_NAMESPACE = "MultipleSelectBox";
	var PLUGIN_STYLE_HORIZONTAL = "horizontal";
	var PLUGIN_STYLE_VERTICAL = "vertical";
	var PLUGIN_STYLE_DISABLED = "disabled";
	var PLUGIN_STYLE_SELECTED = "selected";
	var PLUGIN_STYLE_SELECTING = "selecting";
	var PLUGIN_STYLE_OPTGROUP = "optgroup";
	var PLUGIN_STYLE_OPTGROUPITEM = "optgroupitem";
	var defaultOptions = {
		maxLimit : -1,
		scrollSpeed : 20,
		isHorizontalMode : false,
		isMouseEventEnabled : true,
		isKeyEventEnabled : true,
		/* form options */
		submitField : null,
		valueRendererArray : null,
		/* callback function */
		onCreate : null,
		onSelectStart : null,
		onSelectEnd : null,
		onSelectChange : null
	};

	/**
	 * Public Method
	 */
	$.extend($.fn, {
		/**
		 * Public : Main method
		 * 
		 * @param options
		 *            Object
		 * @return jQuery
		 */
		multipleSelectBox : function(options) {
			options = $.extend({}, defaultOptions, options);
			return this.each(function() {
				var $container = $(this);
				/* prepare className */
				$container.addClass(PLUGIN_NAMESPACE).addClass(options.isHorizontalMode ? PLUGIN_STYLE_HORIZONTAL : PLUGIN_STYLE_VERTICAL);
				/* prepare options */
				$container.data("options", options);
				/* disable text select and give the focus */
				$container.css({
					userSelect : "none",
					KhtmlUserSelect : "none",
					MozUserSelect : "none",
					WebkitUserSelect : "none"
				}).attr({
					unselectable : "on",
					tabindex : 0
				}).bind("selectstart", function() {
					return false;
				});
				/* destroy and recalculate */
				$container.destroyMultipleSelectBox().recalculateMultipleSelectBox();
				/* initialize */
				initializeMultipleSelectBox($container, options);
				/* callback function */
				if (options.onCreate) {
					$container.bind("onCreate", options.onCreate);
				}
				if (options.onSelectStart) {
					$container.bind("onSelectStart", options.onSelectStart);
				}
				if (options.onSelectEnd) {
					$container.bind("onSelectEnd", options.onSelectEnd);
				}
				if (options.onSelectChange) {
					$container.bind("onSelectChange", options.onSelectChange);
				}
				/* prepare the submit field */
				if (options.submitField && typeof options.submitField === "string") {
					var $submitField = $("input[name=" + options.submitField + "]");
					options.submitField = ($submitField.length > 0 ? $submitField : $("<input type='hidden' name='" + options.submitField + "' />").insertAfter($container));
				}
				/* trigger event */
				if (options.onCreate) {
					options.onCreate.apply($container[0]);
				}
			});
		},

		/**
		 * Public : Get container's cached rows
		 * 
		 * @param isReNew
		 *            boolean
		 * @param selector
		 *            String
		 * @return jQuery
		 */
		getMultipleSelectBoxCachedRows : function(isReNew, selector) {
			return this.pushStack($.map(this, function(container) {
				var $container = $(container);
				var $rows = $container.data("rows");
				if (isReNew || !$rows) {
					/* cache rows if necessary */
					$rows = $container.children();
					$container.data("rows", $rows);
				}
				if (selector) {
					$rows = $rows.filter(selector);
				}
				return $rows.get();
			}));
		},

		/**
		 * Public : Get container's selected rows
		 * 
		 * @return jQuery
		 */
		getMultipleSelectBoxSelectedRows : function() {
			return $.grep(this.getMultipleSelectBoxCachedRows(), function(row) {
				var $childRow = $(row);
				return ($childRow.isMultipleSelectBoxRowSelectable() && $childRow.isMultipleSelectBoxRowSelected());
			});
		},

		/**
		 * Public : Get option group row's items
		 * 
		 * @param selector
		 *            String
		 * @return jQuery
		 */
		getMultipleSelectBoxOptGroupItems : function(selector) {
			return this.pushStack($.map(this, function(optGroupRow) {
				var $optGroupRow = $(optGroupRow);
				/* nextUntil */
				var resultArray = [];
				var $childGroupItem = $optGroupRow;
				while (($childGroupItem = $childGroupItem.next()).length > 0 && $childGroupItem.isMultipleSelectBoxRowOptGroupItem()) {
					resultArray.push($childGroupItem[0]);
				}
				if (selector) {
					resultArray = $optGroupRow.pushStack(resultArray).filter(selector).get();
				}
				return resultArray;
			}));
		},

		/**
		 * Public : Get row's index
		 * 
		 * @return Number
		 */
		getMultipleSelectBoxRowIndex : function() {
			return this.data("index");
		},

		/**
		 * Public : Get container's options
		 * 
		 * @return Object
		 */
		getMultipleSelectBoxOptions : function() {
			return this.data("options");
		},

		/**
		 * Public : Draw range
		 * 
		 * @param startIndex
		 *            int
		 * @param currentIndex
		 *            int
		 * @param drawOption
		 *            Object
		 * @return jQuery
		 */
		drawMultipleSelectBox : function(startIndex, currentIndex, drawOption) {
			drawOption = $.extend({
				isGetPositionByCache : false,
				isSelectionOpposite : false,
				isSelectionRetained : false,
				scrollPos : -1
			}, drawOption);
			return this.each(function() {
				var $container = $(this);
				var $rows = $container.getMultipleSelectBoxCachedRows();
				var options = $container.getMultipleSelectBoxOptions();
				/* recalculate position or not */
				if (!drawOption.isGetPositionByCache) {
					$container.recalculateMultipleSelectBox(true, true);
				}
				var containerInfo = $container.data("info");
				var rowSize = containerInfo.rowInfoArray.length;
				/* remove invalid or duplicated request */
				if (startIndex < 0 || currentIndex < 0 || startIndex >= rowSize || currentIndex >= rowSize || options.maxLimit == 0 || !$rows.eq(startIndex).isMultipleSelectBoxRowSelectable()) {
					return this;
				}
				var minIndex = Math.min(startIndex, currentIndex);
				var maxIndex = Math.max(startIndex, currentIndex);
				/* prepare unselected or selecting array */
				var unselectedArray = [];
				var selectingArray = [];
				var selectedCount = 0;
				$rows.each(function(index) {
					var $childRow = $(this);
					$childRow.removeClass(PLUGIN_STYLE_SELECTING);
					if ($childRow.isMultipleSelectBoxRowSelectable()) {
						var isRowSelected = $childRow.isMultipleSelectBoxRowSelected();
						if (minIndex <= index && index <= maxIndex) {
							if (isRowSelected) {
								if (drawOption.isSelectionOpposite) {
									unselectedArray.push($childRow);
								} else {
									selectedCount++;
								}
							} else {
								selectingArray.push($childRow);
							}
						} else {
							if (isRowSelected) {
								if (drawOption.isSelectionRetained) {
									selectedCount++;
								} else {
									unselectedArray.push($childRow);
								}
							}
						}
					}
				});
				var selectingArraySize = selectingArray.length;
				/* calculate max limit */
				if (options.maxLimit > 0 && (selectingArraySize + selectedCount) > options.maxLimit) {
					return this;
				}
				/* reset all style if necessary */
				$rows.eq(currentIndex).addClass(PLUGIN_STYLE_SELECTING);
				for ( var i = 0, unselectedArraySize = unselectedArray.length; i < unselectedArraySize; i++) {
					unselectedArray[i].removeClass(PLUGIN_STYLE_SELECTED);
				}
				for ( var i = 0; i < selectingArraySize; i++) {
					selectingArray[i].addClass(PLUGIN_STYLE_SELECTED);
				}
				/* reset scroll bar */
				var scrollPos = drawOption.scrollPos;
				if (scrollPos != null) {
					var isHorizontalMode = options.isHorizontalMode;
					if (scrollPos < 0) {
						scrollPos = calculatedScrollPosition($container, containerInfo, isHorizontalMode, startIndex, currentIndex, null);
					}
					if (scrollPos != null && scrollPos >= 0) {
						if (isHorizontalMode) {
							$container.scrollLeft(scrollPos);
						} else {
							$container.scrollTop(scrollPos);
						}
					}
				}
				/* reset history */
				containerInfo.lastStartIndex = startIndex;
				containerInfo.lastCurrentIndex = currentIndex;
				return this;
			});
		},

		/**
		 * Public : Serialize all of selected values into an Array
		 * 
		 * @return Array
		 */
		serializeMultipleSelectBoxArray : function() {
			var valueRendererArray = this.getMultipleSelectBoxOptions().valueRendererArray;
			return $.map(this.getMultipleSelectBoxSelectedRows(), function(row) {
				var $childRow = $(row);
				var resultValue = (valueRendererArray != null ? valueRendererArray[$childRow.getMultipleSelectBoxRowIndex()] : null);
				if (resultValue == null) {
					/* trim it for IE6 and IE7 */
					resultValue = $.trim($childRow.text());
				}
				return resultValue;
			});
		},

		/**
		 * Public : Serialize all of selected values
		 * 
		 * @param separator
		 *            String
		 * @return String
		 */
		serializeMultipleSelectBox : function(separator) {
			return this.serializeMultipleSelectBoxArray().join(separator == null ? "," : separator);
		},

		/**
		 * Public : Yield event control
		 * 
		 * @return jQuery
		 */
		yieldMultipleSelectBox : function() {
			$(document).unbind("mouseleave." + PLUGIN_NAMESPACE).unbind("mousemove." + PLUGIN_NAMESPACE);
			return this.unbind("mouseenter").unbind("mouseleave").unbind("mouseover");
		},

		/**
		 * Public : Destroy MultipleSelectBox
		 * 
		 * @return jQuery
		 */
		destroyMultipleSelectBox : function() {
			/* yield event handler */
			return this.yieldMultipleSelectBox().each(function() {
				var $container = $(this);
				/* reset event handler */
				$container.unbind("mousedown").unbind("keydown").unbind("onCreate").unbind("onSelectStart").unbind("onSelectEnd").unbind("onSelectChange");
				/* clear cache */
				var $rows = $container.data("rows");
				if ($rows) {
					$rows.unbind("dblclick").removeData("index");
				}
				$container.removeData("info").removeData("rows");
			});
		},

		/**
		 * Public : Recalculate cached info
		 * 
		 * @param isResetContainerInfo
		 *            boolean
		 * @param isResetRowsInfo
		 *            boolean
		 * @param isResetHistory
		 *            boolean
		 * @param isResetRowCache
		 *            boolean
		 * @return jQuery
		 */
		recalculateMultipleSelectBox : function(isResetContainerInfo, isResetRowsInfo, isResetHistory, isResetRowCache) {
			return this.each(function() {
				var $container = $(this);
				var $rows = $container.getMultipleSelectBoxCachedRows(isResetRowCache);
				var containerInfo = $container.data("info");
				if (!containerInfo) {
					isResetContainerInfo = isResetRowsInfo = isResetHistory = true;
					containerInfo = {};
					/* the info data must existed */
					$container.data("info", containerInfo);
				}
				/* reset all row's position or data */
				if (isResetRowsInfo) {
					var rowInfoArray = [];
					var firstTopPos = -1;
					var firstLeftPost = -1;
					$rows.each(function(index) {
						var $childRow = $(this);
						var childRowOffset = $childRow.offset();
						var childRowTopPos = childRowOffset.top;
						var childRowLeftPos = childRowOffset.left;
						if (index == 0) {
							firstTopPos = childRowTopPos;
							firstLeftPost = childRowLeftPos;
						}
						childRowTopPos -= firstTopPos;
						childRowLeftPos -= firstLeftPost;

						$childRow.data("index", index);
						rowInfoArray.push({
							topPos : childRowTopPos,
							bottomPos : childRowTopPos + $childRow.outerHeight(),
							leftPos : childRowLeftPos,
							rightPos : childRowLeftPos + $childRow.outerWidth()
						});
					});
					containerInfo.rowInfoArray = rowInfoArray;
				}
				/* reset container's position or data */
				if (isResetContainerInfo) {
					var containerOffset = $container.offset();
					containerInfo.topPos = containerOffset.top;
					containerInfo.bottomPos = containerInfo.topPos + $container.outerHeight();
					containerInfo.height = $container.innerHeight();
					containerInfo.scrollHeight = this.scrollHeight;
					containerInfo.leftPos = containerOffset.left;
					containerInfo.rightPos = containerInfo.leftPos + $container.outerWidth();
					containerInfo.width = $container.innerWidth();
					containerInfo.scrollWidth = this.scrollWidth;
				}
				/* reset history data */
				if (isResetHistory) {
					containerInfo.lastStartIndex = containerInfo.lastCurrentIndex = containerInfo.prevStartIndex = containerInfo.prevCurrentIndex = -1;
					containerInfo.prevSelectedArray = null;
				}
			});
		},

		/**
		 * Public : Is container selecting
		 * 
		 * @return boolean
		 */
		isMultipleSelectBoxSelecting : function() {
			return this.hasClass(PLUGIN_STYLE_SELECTING);
		},

		/**
		 * Public : Is row disabled
		 * 
		 * @return boolean
		 */
		isMultipleSelectBoxRowDisabled : function() {
			return this.hasClass(PLUGIN_STYLE_DISABLED);
		},

		/**
		 * Public : Is row selected
		 * 
		 * @return boolean
		 */
		isMultipleSelectBoxRowSelected : function() {
			return this.hasClass(PLUGIN_STYLE_SELECTED);
		},

		/**
		 * Public : Is row selecting
		 * 
		 * @return boolean
		 */
		isMultipleSelectBoxRowSelecting : function() {
			return this.hasClass(PLUGIN_STYLE_SELECTING);
		},

		/**
		 * Public : Is row opt group
		 * 
		 * @return boolean
		 */
		isMultipleSelectBoxRowOptGroup : function() {
			return this.hasClass(PLUGIN_STYLE_OPTGROUP);
		},

		/**
		 * Public : Is row opt group item
		 * 
		 * @return boolean
		 */
		isMultipleSelectBoxRowOptGroupItem : function() {
			return this.hasClass(PLUGIN_STYLE_OPTGROUPITEM);
		},

		/**
		 * Public : Is row selectable
		 * 
		 * @return boolean
		 */
		isMultipleSelectBoxRowSelectable : function() {
			return (!this.isMultipleSelectBoxRowDisabled() && !this.isMultipleSelectBoxRowOptGroup());
		}
	});

	/**
	 * Private : Validate MultipleSelectBox
	 * 
	 * @return jQuery
	 */
	function validateMultipleSelectBox(e) {
		/* yield event handler */
		return $("." + PLUGIN_NAMESPACE).yieldMultipleSelectBox().each(function() {
			var $container = $(this);
			var containerInfo = $container.data("info");
			/* trigger callback */
			if ($container.isMultipleSelectBoxSelecting()) {
				/* reset style */
				$container.removeClass(PLUGIN_STYLE_SELECTING);
				var options = $container.getMultipleSelectBoxOptions();
				var selectedArray = $container.getMultipleSelectBoxSelectedRows();
				if (options.onSelectEnd) {
					options.onSelectEnd.apply($container[0], [ e, selectedArray, containerInfo.lastStartIndex, containerInfo.lastCurrentIndex, containerInfo.prevStartIndex, containerInfo.prevCurrentIndex ]);
				}
				if (options.onSelectChange && (containerInfo.prevSelectedArray == null || getSelectedRowIndexArray(containerInfo.prevSelectedArray).join() != getSelectedRowIndexArray(selectedArray).join())) {
					options.onSelectChange.apply($container[0], [ e, selectedArray, containerInfo.prevSelectedArray, containerInfo.lastStartIndex, containerInfo.lastCurrentIndex, containerInfo.prevStartIndex, containerInfo.prevCurrentIndex ]);
				}
				/* reset the field value */
				if (options.submitField) {
					options.submitField.val($container.serializeMultipleSelectBox());
				}
				containerInfo.prevSelectedArray = selectedArray;
			}
			/* reset history */
			containerInfo.prevStartIndex = containerInfo.lastStartIndex;
			containerInfo.prevCurrentIndex = containerInfo.lastCurrentIndex;
		});
	}

	/**
	 * Private : Initialize MultipleSelectBox
	 * 
	 * @param $container
	 *            jQuery
	 * @param options
	 *            Object
	 * @return jQuery
	 */
	function initializeMultipleSelectBox($container, options) {
		var $document = $(document);
		var $rows = $container.getMultipleSelectBoxCachedRows();
		var containerInfo = $container.data("info");
		/* mouse event */
		if (options.isMouseEventEnabled) {
			/* process container event */
			$container.bind("mousedown", function(e) {
				var $target = $(e.target);
				var $startRow = $target;
				/* correct the focus for chrome */
				if ($target == this) {
					return false;
				} else if ($target.parent()[0] != this) {
					$target[0].focus();
					$startRow = $target.parents("." + PLUGIN_NAMESPACE + ">*").eq(0);
				} else {
					this.focus();
				}
				var startIndex = $startRow.getMultipleSelectBoxRowIndex();
				var currentIndex = startIndex;
				/* trigger callback */
				if (!fireOnSelectStartEvent(e, $container, options, startIndex)) {
					return false;
				}
				/* recalculate container and all row's position */
				$container.recalculateMultipleSelectBox(true, true);
				/* prepare info for drawing */
				var isSelectionOpposite = false;
				var isSelectionRetained = false;
				if (options.isKeyEventEnabled) {
					if (e.shiftKey) {
						currentIndex = startIndex;
						startIndex = containerInfo.lastStartIndex;
					} else if (e.ctrlKey) {
						isSelectionOpposite = isSelectionRetained = true;
					}
				}
				/* reset all style */
				$container.addClass(PLUGIN_STYLE_SELECTING).drawMultipleSelectBox(startIndex, currentIndex, {
					isGetPositionByCache : true,
					isSelectionOpposite : isSelectionOpposite,
					isSelectionRetained : isSelectionRetained,
					scrollPos : null
				});
				/* listening */
				$container.yieldMultipleSelectBox().bind("mouseenter", function() {
					$document.unbind("mousemove." + PLUGIN_NAMESPACE);
				}).bind("mouseleave", function() {
					if (options.scrollSpeed <= 0) {
						return;
					}
					var mouseLeavingDragInfo = {
						mouseX : -1,
						mouseY : -1,
						mouseRangeTotal : 0,
						previousCurrentIndex : currentIndex,
						previousMouseX : -1,
						previousMouseY : -1
					};
					$document.bind("mousemove." + PLUGIN_NAMESPACE, function(e1) {
						mouseLeavingDragInfo.mouseX = e1.pageX;
						mouseLeavingDragInfo.mouseY = e1.pageY;
						currentIndex = calculatedMouseLeavingDragCurrentIndex(options, containerInfo, mouseLeavingDragInfo);
						if (currentIndex >= 0) {
							$container.drawMultipleSelectBox(startIndex, currentIndex, {
								isGetPositionByCache : true,
								isSelectionRetained : isSelectionRetained,
								scrollPos : calculatedScrollPosition($container, containerInfo, options.isHorizontalMode, startIndex, currentIndex, mouseLeavingDragInfo)
							});
							mouseLeavingDragInfo.previousCurrentIndex = currentIndex;
						}
						mouseLeavingDragInfo.previousMouseX = mouseLeavingDragInfo.mouseX;
						mouseLeavingDragInfo.previousMouseY = mouseLeavingDragInfo.mouseY;
					});
				}).bind("mouseover", function(e1) {
					var $childTarget = $(e1.target);
					if (this == $childTarget.parent()[0]) {
						currentIndex = $childTarget.getMultipleSelectBoxRowIndex();
						$container.drawMultipleSelectBox(startIndex, currentIndex, {
							isGetPositionByCache : true,
							isSelectionRetained : isSelectionRetained,
							scrollPos : null
						});
					}
				});
				/* IE hacked for mouse event */
				if ($.browser.msie) {
					$document.bind("mouseleave." + PLUGIN_NAMESPACE, function() {
						$document.one("mousemove." + PLUGIN_NAMESPACE, function(e1) {
							if (!e1.button) {
								validateMultipleSelectBox(e1);
							}
						});
					});
				}
				return false;
			});
			/* select group items automatically */
			$rows.filter("." + PLUGIN_STYLE_OPTGROUP).bind("dblclick", function(e) {
				var $startRow = $(this);
				/* trigger callback */
				if (!fireOnSelectStartEvent(e, $container, options, $startRow.getMultipleSelectBoxRowIndex())) {
					return;
				}
				var childGroupItemList = $startRow.getMultipleSelectBoxOptGroupItems();
				var childGroupItemSelectSize = childGroupItemList.length;
				if (childGroupItemSelectSize > 0) {
					if (options.maxLimit > 0 && childGroupItemSelectSize > options.maxLimit) {
						childGroupItemSelectSize = options.maxLimit;
					}
					$container.drawMultipleSelectBox(childGroupItemList.eq(0).getMultipleSelectBoxRowIndex(), childGroupItemList.eq(childGroupItemSelectSize - 1).getMultipleSelectBoxRowIndex(), {
						scrollPos : null
					});
					/* special case */
					$container.addClass(PLUGIN_STYLE_SELECTING);
					validateMultipleSelectBox(e);
				}
			});
		}
		/* key event */
		if (options.isKeyEventEnabled) {
			$container.bind("keydown", function(e) {
				if (e.target != this) {
					return;
				}
				var keyCode = e.keyCode;
				if ((keyCode >= 37 && keyCode <= 40) || keyCode == 32) {
					var currentIndex = containerInfo.lastCurrentIndex;
					/* prepare info for drawing */
					var isSelectionOpposite = false;
					var isSelectionRetained = e.shiftKey;
					if (keyCode == 37 || keyCode == 38) {
						/* left or up */
						currentIndex = previousSelectableRowIndex($rows, currentIndex);
					} else if (keyCode == 39 || keyCode == 40) {
						/* right or down */
						currentIndex = nextSelectableRowIndex($rows, currentIndex);
					} else if (keyCode == 32) {
						/* white space */
						isSelectionOpposite = true;
					}
					/* trigger callback */
					if (!fireOnSelectStartEvent(e, $container, options, currentIndex)) {
						return;
					}
					$container.addClass(PLUGIN_STYLE_SELECTING).drawMultipleSelectBox(currentIndex, currentIndex, {
						isSelectionOpposite : isSelectionOpposite,
						isSelectionRetained : isSelectionRetained
					});
					validateMultipleSelectBox(e);
					return false;
				}
			});
		}
		return $container;
	}

	/**
	 * Private : Fire OnSelectStart Event
	 * 
	 * @return boolean
	 */
	function fireOnSelectStartEvent(e, $container, options, startIndex) {
		if (!options.onSelectStart) {
			return true;
		}
		var isSelectEnabled = options.onSelectStart.apply($container[0], [ e, startIndex ]);
		return (typeof isSelectEnabled != "boolean" || isSelectEnabled);
	}

	/**
	 * Private : Get Selected Row Index Array
	 * 
	 * @return Array
	 */
	function getSelectedRowIndexArray($rows) {
		return $.map($rows, function(row) {
			return $(row).getMultipleSelectBoxRowIndex();
		});
	}

	/**
	 * Private : Previous Selectable Row Index
	 * 
	 * @return Number
	 */
	function previousSelectableRowIndex($rows, index) {
		var rowSize = $rows.length;
		index -= 1;
		if (index < 0 || index >= rowSize) {
			return -1;
		}
		if (!$rows.eq(index).isMultipleSelectBoxRowSelectable()) {
			return previousSelectableRowIndex($rows, index);
		}
		return index;
	}

	/**
	 * Private : Next Selectable Row Index
	 * 
	 * @return Number
	 */
	function nextSelectableRowIndex($rows, index) {
		var rowSize = $rows.length;
		index += 1;
		if (index < 0 || index >= rowSize) {
			return -1;
		}
		if (!$rows.eq(index).isMultipleSelectBoxRowSelectable()) {
			return nextSelectableRowIndex($rows, index);
		}
		return index;
	}

	/**
	 * Private : Calculated Mouse Leaving Drag Current Index
	 * 
	 * @return Number
	 */
	function calculatedMouseLeavingDragCurrentIndex(options, containerInfo, mouseLeavingDragInfo) {
		var currentIndex = -1;
		var rowSize = containerInfo.rowInfoArray.length;
		var mouseX = mouseLeavingDragInfo.mouseX;
		var mouseY = mouseLeavingDragInfo.mouseY;
		var previousCurrentIndex = mouseLeavingDragInfo.previousCurrentIndex;
		var previousMouseX = mouseLeavingDragInfo.previousMouseX;
		var previousMouseY = mouseLeavingDragInfo.previousMouseY;
		if (options.isHorizontalMode) {
			/* horizontal mode */
			if (mouseX < containerInfo.leftPos) {
				if (previousCurrentIndex > 0 && (previousMouseX < 0 || mouseX < previousMouseX)) {
					mouseLeavingDragInfo.mouseRangeTotal += (containerInfo.leftPos - mouseX) / 5;
					var targetPos = containerInfo.rowInfoArray[previousCurrentIndex].leftPos - (options.scrollSpeed / 20 * mouseLeavingDragInfo.mouseRangeTotal);
					if (targetPos > 0) {
						for ( var i = previousCurrentIndex - 1; i >= 0; i--) {
							if (targetPos >= containerInfo.rowInfoArray[i].leftPos) {
								break;
							}
							currentIndex = i;
							mouseLeavingDragInfo.mouseRangeTotal = 0;
						}
					} else {
						currentIndex = 0;
					}
				}
			} else if (mouseX > containerInfo.rightPos) {
				if (previousCurrentIndex < rowSize - 1 && (previousMouseX < 0 || mouseX > previousMouseX)) {
					mouseLeavingDragInfo.mouseRangeTotal += (mouseX - containerInfo.rightPos) / 5;
					var targetPos = containerInfo.rowInfoArray[previousCurrentIndex].rightPos + (options.scrollSpeed / 20 * mouseLeavingDragInfo.mouseRangeTotal);
					if (targetPos < containerInfo.scrollWidth) {
						for ( var i = previousCurrentIndex + 1; i < rowSize; i++) {
							if (targetPos < containerInfo.rowInfoArray[i].rightPos) {
								break;
							}
							currentIndex = i;
							mouseLeavingDragInfo.mouseRangeTotal = 0;
						}
					} else {
						currentIndex = rowSize - 1;
					}
				}
			}
		} else {
			/* vertical mode */
			if (mouseY < containerInfo.topPos) {
				if (previousCurrentIndex > 0 && (previousMouseY < 0 || mouseY < previousMouseY)) {
					mouseLeavingDragInfo.mouseRangeTotal += (containerInfo.topPos - mouseY) / 5;
					var targetPos = containerInfo.rowInfoArray[previousCurrentIndex].topPos - (options.scrollSpeed / 20 * mouseLeavingDragInfo.mouseRangeTotal);
					if (targetPos > 0) {
						for ( var i = previousCurrentIndex - 1; i >= 0; i--) {
							if (targetPos >= containerInfo.rowInfoArray[i].topPos) {
								break;
							}
							currentIndex = i;
							mouseLeavingDragInfo.mouseRangeTotal = 0;
						}
					} else {
						currentIndex = 0;
					}
				}
			} else if (mouseY > containerInfo.bottomPos) {
				if (previousCurrentIndex < rowSize - 1 && (previousMouseY < 0 || mouseY > previousMouseY)) {
					mouseLeavingDragInfo.mouseRangeTotal += (mouseY - containerInfo.bottomPos) / 5;
					var targetPos = containerInfo.rowInfoArray[previousCurrentIndex].bottomPos + (options.scrollSpeed / 20 * mouseLeavingDragInfo.mouseRangeTotal);
					if (targetPos < containerInfo.scrollHeight) {
						for ( var i = previousCurrentIndex + 1; i < rowSize; i++) {
							if (targetPos < containerInfo.rowInfoArray[i].bottomPos) {
								break;
							}
							currentIndex = i;
							mouseLeavingDragInfo.mouseRangeTotal = 0;
						}
					} else {
						currentIndex = rowSize - 1;
					}
				}
			}
		}
		return currentIndex;
	}

	/**
	 * Private : Calculated Scroll Position
	 * 
	 * @return Number
	 */
	function calculatedScrollPosition($container, containerInfo, isHorizontalMode, startIndex, currentIndex, mouseLeavingDragInfo) {
		var scrollPos = null;
		var rowSize = containerInfo.rowInfoArray.length;
		if (startIndex < 0 || currentIndex < 0 || startIndex >= rowSize || currentIndex >= rowSize) {
			return null;
		}
		var mouseX = null;
		var mouseY = null;
		if (mouseLeavingDragInfo != null) {
			mouseX = mouseLeavingDragInfo.mouseX;
			mouseY = mouseLeavingDragInfo.mouseY;
		}
		var currentRow = containerInfo.rowInfoArray[currentIndex];
		var lastCurrentIndex = containerInfo.lastCurrentIndex;
		if (isHorizontalMode) {
			/* horizontal mode */
			if (mouseX == null) {
				var currentScrollPos = $container[0].scrollLeft;
				if (startIndex < currentIndex || (startIndex == currentIndex && lastCurrentIndex >= 0 && lastCurrentIndex < currentIndex)) {
					if (currentRow.rightPos < currentScrollPos || currentRow.rightPos - containerInfo.width > currentScrollPos) {
						scrollPos = currentRow.rightPos - containerInfo.width;
					}
				} else if (startIndex > currentIndex || (startIndex == currentIndex && (lastCurrentIndex < 0 || lastCurrentIndex > currentIndex))) {
					if (currentRow.leftPos < currentScrollPos || currentRow.leftPos + containerInfo.width < currentScrollPos) {
						scrollPos = currentRow.leftPos;
					}
				}
			} else if (mouseX < containerInfo.leftPos) {
				scrollPos = currentRow.leftPos;
			} else if (mouseX > containerInfo.rightPos) {
				scrollPos = currentRow.rightPos - containerInfo.width;
			}
		} else {
			/* vertical mode */
			if (mouseY == null) {
				var currentScrollPos = $container[0].scrollTop;
				if (startIndex < currentIndex || (startIndex == currentIndex && lastCurrentIndex >= 0 && lastCurrentIndex < currentIndex)) {
					if (currentRow.bottomPos < currentScrollPos || currentRow.bottomPos - containerInfo.height > currentScrollPos) {
						scrollPos = currentRow.bottomPos - containerInfo.height;
					}
				} else if (startIndex > currentIndex || (startIndex == currentIndex && (lastCurrentIndex < 0 || lastCurrentIndex > currentIndex))) {
					if (currentRow.topPos < currentScrollPos || currentRow.topPos + containerInfo.height < currentScrollPos) {
						scrollPos = currentRow.topPos;
					}
				}
			} else if (mouseY < containerInfo.topPos) {
				scrollPos = currentRow.topPos;
			} else if (mouseY > containerInfo.bottomPos) {
				scrollPos = currentRow.bottomPos - containerInfo.height;
			}
		}
		return scrollPos;
	}

	/**
	 * Global Event Control
	 */
	$(document).bind("mouseup." + PLUGIN_NAMESPACE, function(e) {
		validateMultipleSelectBox(e);
	});
})(jQuery);